//////////////////////////////////////////////////////////////////////////
#pragma once


//////////////////////////////////////////////////////////////////////////
// Include lesser types.
#include "Mmx.hpp"


#if defined( _WINDOWS ) || (defined( __APPLE__ ) && defined( __SSE__ ))
namespace Math
{
	namespace Simd
	{
		//////////////////////////////////////////////////////////////////////////
		// Sse base implementation.
		struct Sse
		{
			typedef __m128							F32x4_t;


			static bool								IsValid( F32x4_t v )
			{
				F32x4_t		test	=	_mm_mul_ps( v, _mm_setzero_ps() );
				test				=	_mm_cmpeq_ps( test, _mm_setzero_ps() );
				int			mask	=	_mm_movemask_ps( test );
				return( 0x0f == mask );
			}

#if defined( SIMD_ADVANCED_DEBUG )
			static void								Validate( F32x4_t v )	{if( !IsValid( (v) ) ) CORE_BREAK;}
#else
			static void								Validate( F32x4_t v )	{}
#endif

			static F32x4_t							Create( float x, float y, float z )
			{
				F32x4_t		result	=	{x, y, z, 0.0f};
				Validate( result );
				return result;
			}

			static F32x4_t							Create( float x, float y, float z, float w )
			{
				F32x4_t		result	=	{x, y, z, w};
				Validate( result );
				return result;
			}

			static float							Fetch( const F32x4_t v, size_t idx )
			{
				assert( idx < 4 );
				Validate( v );

				F32x4_t	result;

				switch( idx )
				{
				case 3:	result	=	_mm_castsi128_ps( _mm_srli_si128( _mm_castps_si128( v ), 12 ) );	break;
				case 2:	result	=	_mm_castsi128_ps( _mm_srli_si128( _mm_castps_si128( v ), 8 ) );		break;
				case 1:	result	=	_mm_castsi128_ps( _mm_srli_si128( _mm_castps_si128( v ), 4 ) );		break;
				case 0:	result	=	v;
				}

				float	sresult;
				_mm_store_ss( &sresult, result );
				return sresult;
			}

			static F32x4_t							Store( F32x4_t v, float f, size_t idx )
			{
				assert( idx < 4 );
				Validate( v );

				F32x4_t		zeroLane;
				F32x4_t		insertion;
				switch( idx )
				{
				case 3:	{zeroLane	=	Create( 1.0f, 1.0f, 1.0f, 0.0f );	insertion	=	Create( 0.0f, 0.0f, 0.0f, f );}	break;
				case 2:	{zeroLane	=	Create( 1.0f, 1.0f, 0.0f, 1.0f );	insertion	=	Create( 0.0f, 0.0f, f, 0.0f );}	break;
				case 1:	{zeroLane	=	Create( 1.0f, 0.0f, 1.0f, 1.0f );	insertion	=	Create( 0.0f, f, 0.0f, 0.0f );}	break;
				case 0:	{zeroLane	=	Create( 0.0f, 1.0f, 1.0f, 1.0f );	insertion	=	Create( f, 0.0f, 0.0f, 0.0f );}	break;
				}

				F32x4_t		result	=	_mm_mul_ps( v, zeroLane );
				result				=	_mm_or_ps( result, insertion );
				return result;
			}

			static F32x4_t							Negate( const F32x4_t v )
			{
				F32x4_t		negation	=	{-1.0f, -1.0f, -1.0f, 0.0f};
				F32x4_t		result		=	_mm_mul_ps( v, negation );
				Validate( result );
				return result;
			}

			static F32x4_t							Add( F32x4_t lhs, F32x4_t rhs )
			{
				F32x4_t		result		=	_mm_add_ps( lhs, rhs );
				Validate( result );
				return result;
			}

			static F32x4_t							Sub( F32x4_t lhs, F32x4_t rhs )
			{
				F32x4_t		result		=	_mm_sub_ps( lhs, rhs );
				Validate( result );
				return result;
			}

			static F32x4_t							Mul( F32x4_t lhs, F32x4_t rhs )
			{
				F32x4_t		result		=	_mm_mul_ps( lhs, rhs );
				Validate( result );
				return result;
			}

			static F32x4_t							Div( F32x4_t lhs, F32x4_t rhs )
			{
				F32x4_t		result		=	_mm_div_ps( lhs, rhs );
				Validate( result );
				return result;
			}

			// optimization pass:
			//	instruction costs (worst:best)
			//	_mm_shuffle_ps	6-2	:	1-1
			//	_mm_movehl_ps	6-2 :	1-.33
			//	_mm_add_ps		5-2	:	3-1

			static float							Dot3( F32x4_t lhs, F32x4_t rhs )
			{
				Validate( lhs );	Validate( rhs );

				__m128	result				=	_mm_mul_ps( lhs, rhs );
				__m128	p1					=	_mm_movehl_ps( result, result );								// Move z(2) component into x(0).
				__m128	p2					=	_mm_shuffle_ps( result, result, _MM_SHUFFLE( 3, 3, 3, 1 ) );	// Move y(1) component into x(0).

				// Add horizontally.
				__m128	a1					=	_mm_add_ps( result, p1 );
				result						=	_mm_add_ps( a1, p2 );

				Validate( result );

				// Save the results to a float.
				float				sresult;
				_mm_store_ss( &sresult, result );

				return sresult;
			}

			static float							Mag3( F32x4_t lhs )
			{
				Validate( lhs );

				__m128	result				=	_mm_mul_ps( lhs, lhs );
				__m128	p1					=	_mm_movehl_ps( result, result );								// Move z(2) component into x(0).
				__m128	p2					=	_mm_shuffle_ps( result, result, _MM_SHUFFLE( 3, 3, 3, 1 ) );	// Move y(1) component into x(0).

				// Add horizontally.
				__m128	a1					=	_mm_add_ps( result, p1 );
				result						=	_mm_add_ps( a1, p2 );
				result						=	_mm_sqrt_ss( result );

				Validate( result );

				float				sresult;
				_mm_store_ss( &sresult, result );
				return sresult;
			}

			static F32x4_t							Norm3( F32x4_t lhs )
			{
				Validate( lhs );

				__m128	result				=	_mm_mul_ps( lhs, lhs );
				__m128	p1					=	_mm_movehl_ps( result, result );								// Move z(2) component into x(0).
				__m128	p2					=	_mm_shuffle_ps( result, result, _MM_SHUFFLE( 3, 3, 3, 1 ) );	// Move y(1) component into x(0).

				// Add horizontally.
				__m128	a1					=	_mm_add_ps( result, p1 );
				result						=	_mm_add_ps( a1, p2 );
				result						=	_mm_sqrt_ss( result );

				result						=	_mm_shuffle_ps( result, result, _MM_SHUFFLE( 0, 0, 0, 0 ) );

				result						=	_mm_div_ps( lhs, result );

				Validate( result );
				return result;
			}

			static F32x4_t							Cross3( F32x4_t lhs, F32x4_t rhs )
			{
				Validate( lhs );	Validate( rhs );

				static const int	swizzle1	=	_MM_SHUFFLE( 3, 0, 2, 1 );
				static const int	swizzle2	=	_MM_SHUFFLE( 3, 1, 0, 2 );

				__m128	v1	=	_mm_shuffle_ps( lhs, lhs, swizzle1 );
				__m128	v2	=	_mm_shuffle_ps( rhs, rhs, swizzle2 );
				__m128	v3	=	_mm_shuffle_ps( lhs, lhs, swizzle2 );
				__m128	v4	=	_mm_shuffle_ps( rhs, rhs, swizzle1 );

				__m128	p1	=	_mm_mul_ps( v1, v2 );
				__m128	p2	=	_mm_mul_ps( v3, v4 );

				__m128	result	=	_mm_sub_ps( p1, p2 );
				Validate( result );
				return result;
			}
		};
	}
}
#endif
